using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace PatternMaker
{
	public partial class Form1 : Form
	{
		int cellCountHorizontal;
		int cellCountVertical;
		int cellHeight;
		int cellWidth;
		Bitmap colourChart;
		int colourChartItemHeight = 30;
		Bitmap original;
		Bitmap pattern;
		Bitmap rawPattern;
		Bitmap patternThumbnail;
		Dictionary<int, char> colourCodes = new Dictionary<int, char>();
		int colourTolerance = 25;
		int scale = 1;
		char[,] symbolChart;
		bool useSymbols;

		public Form1()
		{
			InitializeComponent();
		}


		private void ApplyGrid(Image image)
		{
			Graphics g = Graphics.FromImage(image);
			Pen p = new Pen(Color.Black);

			for (int x = 0; x <= image.Width; x += cellWidth * scale)
			{
				g.DrawLine(p, x, 0, x, image.Height);
			}

			for (int y = 0; y <= image.Height; y += cellHeight * scale)
			{
				g.DrawLine(p, 0, y, image.Height, y);
			}
		}

		private char GetColourCode(ref Color color)
		{
			char code = (char)0;

			bool found = false;
			int rtolerance;
			int gtolerance;
			int btolerance;

			byte findA = color.A;
			byte findR = color.R;
			byte findG = color.G;
			byte findB = color.B;

			foreach (int argb in colourCodes.Keys)
			{
				byte a = Color.FromArgb(argb).A;
				byte r = Color.FromArgb(argb).R;
				byte g = Color.FromArgb(argb).G;
				byte b = Color.FromArgb(argb).B;

				bool foundR = false;
				rtolerance = 0;
				do
				{
					if (r == findR - rtolerance || r == findR + rtolerance)
					{
						foundR = true;
					}
					else rtolerance++;
				}
				while (!foundR && rtolerance <= colourTolerance);

				bool foundG = false;
				gtolerance = 0;
				do
				{
					if (g == findG - gtolerance || g == findG + gtolerance)
					{
						foundG = true;
					}
					else gtolerance++;
				}
				while (!foundG && gtolerance <= colourTolerance);

				bool foundB = false;
				btolerance = 0;
				do
				{
					if (b == findB - btolerance || b == findB + btolerance)
					{
						foundB = true;
					}
					else btolerance++;
				}
				while (!foundB && btolerance <= colourTolerance);

				if (foundR && foundG && foundB)
				{
					code = colourCodes[argb];
				  color = Color.FromArgb(argb);
					found = true;
				  break;
				}
			}

			if (!found)
			{
				code = (char)(colourCodes.Count + 65);
				int argb = color.ToArgb();
				colourCodes[argb] = code;
			}

			return code;
		}

		private void HandleException(Exception ex)
		{
			MessageBox.Show(ex.ToString(), Text, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
		}

		private void InitializeSettings()
		{
			cellCountHorizontal = (int)ntxtCellCountHorizontal.Value;
			cellCountVertical = (int)ntxtCellCountVertical.Value;
			cellHeight = original.Height / cellCountHorizontal;
			cellWidth = original.Width / cellCountVertical;
			colourTolerance = (int)ntxtColourTolerance.Value;
			useSymbols = chkUseSymbols.Checked;
		}

		private void InitializeOriginalSettings()
		{
			InitializeSettings();
			scale = (int)ntxtOriginalScale.Value;
		}

		private void InitializePatternSettings()
		{
			InitializeSettings();
			scale = (int)ntxtPatternScale.Value;
		}

		private char NormalizeColour(Rectangle r)
		{
			// get colour counts
			Dictionary<Color, int> colourCounts = new Dictionary<Color, int>();
			Color pixelColour;
			for (int x = r.Left; x < r.Right && x < original.Width; x++)
			{
				for (int y = r.Top; y < r.Bottom && y < original.Height; y++)
				{
					pixelColour = original.GetPixel(x, y);
					if (colourCounts.ContainsKey(pixelColour)) colourCounts[pixelColour]++;
					else colourCounts[pixelColour] = 1;
				}
			}

			// pick colour with highest frequency
			Color bestColour = Color.White;
			int bestCount = 0;
			foreach (Color keyColour in colourCounts.Keys)
			{
				if (colourCounts[keyColour] > bestCount)
				{
					bestColour = keyColour;
					bestCount = colourCounts[keyColour];
				}
			}

			// add colour to colour codes
			char code = GetColourCode(ref bestColour);

			// thumbnail, fill cell with normalized colour
			Graphics g = Graphics.FromImage(patternThumbnail);
			g.FillRectangle(new SolidBrush(bestColour), r);
			g.Flush();

			// fill cell with symbol
			if (!useSymbols)
			{
				// fill cell with normalized colour
				g = Graphics.FromImage(rawPattern);
				g.FillRectangle(new SolidBrush(bestColour), r);
				g.Flush();
			//}
			//else
			//{
			//  g = Graphics.FromImage(rawPattern);
			//  g.FillRectangle(new SolidBrush(Color.White), r);
			//  Font f = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
			//  //Font f = Font;
			//  g.DrawString(new string(new char[] { code }), f, new SolidBrush(Color.Black), r.Left, r.Top);
			//  g.Flush();
			}

			return code;
		}

		private void RefreshFiles()
		{
			lstFiles.Items.Clear();
			if (Directory.Exists(txtPath.Text))
			{
				FilenameFilter filenameFilter = cboFilter.SelectedItem as FilenameFilter;
				foreach (string filter in filenameFilter.Filters)
				{
					foreach (string fullFilename in Directory.GetFiles(txtPath.Text, filter))
					{
						string filename = Path.GetFileName(fullFilename);
						if (!lstFiles.Items.Contains(filename)) lstFiles.Items.Add(filename);
					}
				}
			}
		}

		private void RefreshOriginal()
		{
			Bitmap griddedOriginal = (Bitmap)original.Clone();
			griddedOriginal = Scale(griddedOriginal, scale);
			ApplyGrid(griddedOriginal);
			pbOriginalPreview.Image = griddedOriginal;
		}

		private void RefreshPattern()
		{
			VoidHandler del = delegate()
			{
				if (rawPattern != null)
				{
					if (chkUseSymbols.Checked && symbolChart != null)
					{
						pattern = new Bitmap(rawPattern.Width * scale, rawPattern.Height * scale);
						Rectangle cellRectangle = new Rectangle();
						cellRectangle.Height = cellHeight;
						cellRectangle.Width = cellWidth;
						Graphics g = Graphics.FromImage(pattern);
						g.FillRectangle(new SolidBrush(Color.White), g.ClipBounds);

						int cellX = 0;
						int cellY;
						char code;
						for (int x = 0; x < pattern.Width; x += cellWidth * scale)
						{
							cellY = 0;
							for (int y = 0; y < pattern.Height; y += cellHeight * scale)
							{
								if (cellX < symbolChart.GetLength(0) && cellY < symbolChart.GetLength(1))
								{
									code = symbolChart[cellX, cellY];
									cellRectangle.X = x;
									cellRectangle.Y = y;
									g = Graphics.FromImage(pattern);
									g.FillRectangle(new SolidBrush(Color.White), cellRectangle);
									Font f = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Bold);
									//Font f = Font;
									g.DrawString(new string(new char[] { code }), f, new SolidBrush(Color.Black), cellRectangle.Left, cellRectangle.Top);
									g.Flush();
								}
								cellY++;
							}
							cellX++;
						}
					}
					else
					{
						pattern = (Bitmap)rawPattern.Clone();
						pattern = Scale(pattern, scale);
					}
					ApplyGrid(pattern);
					pbPattern.Image = pattern;
				}
			};
			if (InvokeRequired) Invoke(del);
			else del();
		}

		private Bitmap Scale(Bitmap imgPhoto, int scale)
		{
			int Percent = (int)(scale * 100);
			float nPercent = ((float)Percent / 100);

			int sourceWidth = imgPhoto.Width;
			int sourceHeight = imgPhoto.Height;
			int sourceX = 0;
			int sourceY = 0;

			int destX = 0;
			int destY = 0;
			int destWidth = (int)(sourceWidth * nPercent);
			int destHeight = (int)(sourceHeight * nPercent);

			Bitmap bmPhoto = new Bitmap(destWidth, destHeight,
															 PixelFormat.Format24bppRgb);
			bmPhoto.SetResolution(imgPhoto.HorizontalResolution,
															imgPhoto.VerticalResolution);

			Graphics grPhoto = Graphics.FromImage(bmPhoto);
			grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;

			grPhoto.DrawImage(imgPhoto,
					new Rectangle(destX, destY, destWidth, destHeight),
					new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
					GraphicsUnit.Pixel);

			grPhoto.Dispose();
			return bmPhoto;
		}


		private void Form1_Load(object sender, EventArgs e)
		{
			try
			{
				FilenameFilter picturesFilter = new FilenameFilter();
				picturesFilter.Name = "Picture Files (*.jpg,*.gif,*.png,*.bmp)";
				picturesFilter.Filters.Add("*.bmp");
				picturesFilter.Filters.Add("*.gif");
				picturesFilter.Filters.Add("*.jpg");
				picturesFilter.Filters.Add("*.png");
				cboFilter.Items.Add(picturesFilter);
				cboFilter.SelectedItem = picturesFilter;

				FilenameFilter allFilesFilter = new FilenameFilter();
				allFilesFilter.Name = "All Files (*.*)";
				allFilesFilter.Filters.Add("*.*");
				cboFilter.Items.Add(allFilesFilter);

				txtPath.Text = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}


		#region tpFile event handlers

		private void btnBrowsePath_Click(object sender, EventArgs e)
		{
			try
			{
				if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
				{
					txtPath.Text = folderBrowserDialog1.SelectedPath;
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnClose_Click(object sender, EventArgs e)
		{
			try
			{
				Close();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnNextFile_Click(object sender, EventArgs e)
		{
			try
			{
				InitializeOriginalSettings();
				RefreshOriginal();
				tcWizard.SelectedTab = tpOriginalPreview;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void cboFilter_SelectedIndexChanged(object sender, EventArgs e)
		{
			try
			{
				RefreshFiles();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void lstFiles_SelectedIndexChanged(object sender, EventArgs e)
		{
			try
			{
				if (lstFiles.SelectedItem != null)
				{
					original = new Bitmap(Path.Combine(txtPath.Text, (string)lstFiles.SelectedItem));
					pbLoadPreview.Image = original;
					btnNextFile.Enabled = true;
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void txtPath_TextChanged(object sender, EventArgs e)
		{
			try
			{
				RefreshFiles();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		#endregion

		#region tpOriginal event handlers

		private void btnOriginalNext_Click(object sender, EventArgs e)
		{
			try
			{
				lblProgress.Text = "0%";
				progressBar1.Value = 0;
				tcWizard.SelectedTab = tpGenerating;
				ntxtPatternScale.Value = scale;
				backgroundWorker1.RunWorkerAsync();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnOriginalPrevious_Click(object sender, EventArgs e)
		{
			try
			{
				tcWizard.SelectedTab = tpFile;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void chkAdvancedSettings_CheckedChanged(object sender, EventArgs e)
		{
			try
			{
				lblColourTolerance.Visible = chkAdvancedSettings.Checked;
				ntxtColourTolerance.Visible = chkAdvancedSettings.Checked;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void ntxtCellCountHorizontal_ValueChanged(object sender, EventArgs e)
		{
			try
			{
				InitializeOriginalSettings();
				RefreshOriginal();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void ntxtCellCountVertical_ValueChanged(object sender, EventArgs e)
		{
			try
			{
				InitializeOriginalSettings();
				RefreshOriginal();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void ntxtColourTolerance_ValueChanged(object sender, EventArgs e)
		{
			try
			{
				InitializeOriginalSettings();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void ntxtOriginalScale_ValueChanged(object sender, EventArgs e)
		{
			try
			{
				InitializeOriginalSettings();
				RefreshOriginal();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		#endregion

		#region tpGenerating event handlers

		private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
		{
			try
			{
				symbolChart = new char[cellCountHorizontal, cellCountVertical];
				rawPattern = (Bitmap)original.Clone();
				patternThumbnail = (Bitmap)original.Clone();
				colourCodes.Clear();

				decimal totalCells = (rawPattern.Width / cellWidth) * (rawPattern.Height / cellHeight);
				decimal increment = 100M / totalCells;
				decimal cellCount = 0;
				Rectangle cellRectangle = new Rectangle();
				cellRectangle.Height = cellHeight;
				cellRectangle.Width = cellWidth;
				char code;
				int cellX = 0;
				int cellY;
				for (int x = 0; x < rawPattern.Width; x += cellWidth)
				{
					cellY = 0;
					for (int y = 0; y < rawPattern.Height; y += cellHeight)
					{
						cellRectangle.X = x;
						cellRectangle.Y = y;
						code = NormalizeColour(cellRectangle);
						if (cellX < symbolChart.GetLength(0) && cellY < symbolChart.GetLength(1))
						{
							symbolChart[cellX, cellY] = code;
						}
						cellCount++;
						backgroundWorker1.ReportProgress((int)(increment * cellCount));
						cellY++;
					}
					cellX++;
				}
				RefreshPattern();

				// create colour chart
				colourChart = new Bitmap(100, colourChartItemHeight * colourCodes.Count);
				int i = 0;
				Graphics g = Graphics.FromImage(colourChart);
				g.FillRectangle(new SolidBrush(Color.White), g.ClipBounds);
				Brush b = new SolidBrush(Color.Black);
				Color colour;
				foreach (int argb in colourCodes.Keys)
				{
					colour = Color.FromArgb(argb);
					code = colourCodes[argb];
					g.DrawString(new string(new char[] { code }), Font, b, 10, i * colourChartItemHeight);
					g.FillRectangle(new SolidBrush(colour), 20, i * colourChartItemHeight, 20, colourChartItemHeight - 10);
					i++;
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			try
			{
				if (e.ProgressPercentage < progressBar1.Minimum)
				{
					lblProgress.Text = progressBar1.Minimum.ToString() + "%";
					progressBar1.Value = progressBar1.Minimum;
				}
				else if (e.ProgressPercentage > progressBar1.Maximum)
				{
					lblProgress.Text = progressBar1.Maximum.ToString() + "%";
					progressBar1.Value = progressBar1.Maximum;
				}
				else
				{
					lblProgress.Text = e.ProgressPercentage.ToString() + "%";
					progressBar1.Value = e.ProgressPercentage;
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			try
			{
				//btnCancel.Enabled = false;
				pbOriginalThumbnail.Image = original;
				pbPattern.Image = pattern;
				pbPatternThumbnail.Image = patternThumbnail;
				pbColours.Image = colourChart;
				gbColours.Text = colourCodes.Count.ToString() + " colours";
				//btnCreatePattern.Enabled = true;
				//btnNextFile.Enabled = true;
				tcWizard.SelectedTab = tpPatternPreview;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}
		
		private void btnCancel_Click(object sender, EventArgs e)
		{
			try
			{
				backgroundWorker1.CancelAsync();
				tcWizard.SelectedTab = tpOriginalPreview;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		#endregion

		#region tpPattern event handlers

		private void btnPatternPrevious_Click(object sender, EventArgs e)
		{
			try
			{
				tcWizard.SelectedTab = tpOriginalPreview;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnPatternRestart_Click(object sender, EventArgs e)
		{
			try
			{
				tcWizard.SelectedTab = tpFile;
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnSaveColours_Click(object sender, EventArgs e)
		{
			try
			{
				if (saveFileDialog1.ShowDialog() == DialogResult.OK)
				{
					colourChart.Save(saveFileDialog1.FileName, ImageFormat.Jpeg);
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void btnSavePattern_Click(object sender, EventArgs e)
		{
			try
			{
				if (saveFileDialog1.ShowDialog() == DialogResult.OK)
				{
					pattern.Save(saveFileDialog1.FileName, ImageFormat.Jpeg);
				}
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		private void ntxtPatternScale_ValueChanged(object sender, EventArgs e)
		{
			try
			{
				InitializePatternSettings();
				RefreshPattern();
			}
			catch (Exception ex)
			{
				HandleException(ex);
			}
		}

		#endregion
	}

	public delegate void VoidHandler();
}